#ifndef   __MATRIX_H__
#define   __MATRIX_H__

#include <cmath>

enum MemoryDevice { RamMemory, GlobalMemory };
enum preMatrix { MatrixT, Matrix }; // chuyen vi, khong chuyen vi

extern "C"
void MulNum( float lamda, int nsize, float* arr );

extern "C"
void AddMatrix( int nsize, float* arr1, float* arr2 );

extern "C"
void SubMatrix( int nsize, float* arr1, float* arr2 );

extern "C"
void addNewColum( int nrows, int ncols, float* arr1, float* arr2, int f );

extern "C"
void SubColum( int nrows, int ncols, float* arr1, float* arr2 );

extern "C"
void Logarit( int nsize, float* arr );

extern "C"
void Sigmoid( int nsize, float* arr );

extern "C"
void MulDirect( int nsize, float* arr1, float* arr2 );

extern "C"
void ResulSigmoidGra( int nsize, float* arr1, float* arr2 );

extern "C"
void ResulExp( int nsize, float* arr );

extern "C"
float SumArray( int nsize, float* arr );

extern "C"
void initialize( int nsize, float* &des, float* arr, MemoryDevice mem );

extern "C"
void CopyData( int size, float* &des, float* arr, MemoryDevice mem );

extern "C"
void finalize( float* mtr );

extern "C"
void resultMatrixMulMatrix( float* dIn1, float* dIn2, float* dOut, int numRows1, int numCols1, int numRows2, int numCols2 );

extern "C"
void resultMatrixMulMatrixT( float* dIn1, float* dIn2, float* dOut, int numRows1, int numCols1, int numRows2, int numCols2 );

extern "C"
void resultMatrixTMulMatrix( float* dIn1, float* dIn2, float* dOut, int numRows1, int numCols1, int numRows2, int numCols2 );

extern "C"
void copToHost( int nsize, float* mtr, float* arr );

class MATRIX {

	private:
		int nrow;
		int ncol;
		float* mtr;

	public:
		// constants
		static const unsigned int CTA_SIZE = 256; // Number of threads per block
		static const unsigned int WARP_SIZE = 32;

	public:
		//------------------------------------------------------------------------
		MATRIX(  ){
		
			this->nrow = 0;
			this->ncol = 0;
			this->mtr = 0;
		}

		MATRIX( int _nrow, int _ncolum, float* arr = 0, MemoryDevice mem = RamMemory ) {
		
			newMatrix( _nrow, _ncolum, arr, mem );
		}

		void newMatrix( int _nrow, int _ncolum, float* arr = 0, MemoryDevice mem = RamMemory ) {

			if( this->mtr != 0 ) {

				finalize( this->mtr );
			}

			this->nrow = _nrow;
			this->ncol = _ncolum;

			initialize( this->getSize(  ), this->mtr, arr, mem );
		}

		void copy( float* arr, MemoryDevice mem = RamMemory ) {

			CopyData( this->getSize()*sizeof(float), this->mtr, arr, mem );
		}
		//------------------------------------------------------------------------

		void mulNumber( float f ) {
			
			MulNum( f, this->getSize(  ), this->mtr );
		}

		void mulMatrixDirect( MATRIX &other ) {

			MulDirect( this->getSize(  ), this->mtr, other.mtr );
		}

		//de dam bao hai ma tran A va B co the thuc hien phep nhan, ta se dieu chinh kich thuoc col cua A.
		void resulMulMatrix( MATRIX &other1, MATRIX &other2, preMatrix mt1, preMatrix mt2 ) {
		
			if( mt1 == Matrix ) {

				if( mt2 == Matrix ) {
				
					this->nrow = other1.getRow(  );
					this->ncol = other2.getCol(  );

					resultMatrixMulMatrix( other1.getMtr(), other2.getMtr(), this->mtr, other1.getRow(), other1.getCol(), other2.getRow(), other2.getCol() );
				}
				else {
				
					this->nrow = other1.getRow(  );
					this->ncol = other2.getRow(  );

					resultMatrixMulMatrixT( other1.getMtr(), other2.getMtr(), this->mtr, other1.getRow(), other1.getCol(), other2.getRow(), other2.getCol() );
				}
			}
			else {
			
				if( mt2 == Matrix ) {
				
					this->nrow = other1.getCol(  );
					this->ncol = other2.getCol(  );

					resultMatrixTMulMatrix( other1.getMtr(), other2.getMtr(), this->mtr, other1.getRow(), other1.getCol(), other2.getRow(), other2.getCol() );
				}
				else {}
			}
		}

		void resulAddNewColum( MATRIX &other, int f ) {

			this->nrow = other.nrow;
			this->ncol = other.ncol + 1;

			addNewColum( this->getRow(), this->getCol(), other.mtr, this->mtr, f );
		}

		void resulSubColum( MATRIX &other ) {

			this->nrow = other.nrow;
			this->ncol = other.ncol - 1;

			SubColum( other.getRow(), other.getCol(), other.mtr, this->mtr );
		}

		void addMatrix( MATRIX &other ) {
		
			AddMatrix( this->getSize(  ), this->mtr, other.mtr );
		}

		void subMatrix( MATRIX &other ) {
		
			SubMatrix( this->getSize(  ), this->mtr, other.mtr );
		}

		void log(  ) {
		
			Logarit( this->getSize(  ), this->mtr );
		}

		void sigmoid(  ) {
		
			Sigmoid( this->getSize(  ), this->mtr );
		}

		void resulSigmoidGradient( MATRIX &other ) {

			ResulSigmoidGra( this->getSize(  ), other.mtr, this->mtr );
		}

		void exp(  ) {

			ResulExp( this->getSize(  ), this->mtr );
		}

		void toArray( float* arr ) {

			copToHost( this->getSize(), this->getMtr(), arr );
		}

		float sum(  ) {

			if( this->getSize() <= 200 ) {

				float* m = new float [this->getSize()];
				float s = 0;

				this->toArray( m );
				for( int i = 0; i < this->getSize(); ++i ) {

					s += m[i];
				}

				delete []m;
				return s;
			}
			else {

				return SumArray( this->getSize(), this->getMtr() );
			}
		}

		int whereMax(  ) {

			if( this->getSize() <= 200 ) {

				float* m = new float [this->getSize()];
				int l = 0;

				this->toArray( m );
				for( int i = 1; i < this->getSize(); ++i ) {

					if( m[i] > m[l] ) {
					
						l = i;
					}
				}

				delete []m;
				return l;
			}
			else { return -1; }
		}


		int getRow(  ) { return this->nrow; }
		int getCol(  ) { return this->ncol; }
		float* getMtr(  ) { return this->mtr;  }
		int getSize(  ) { return this->nrow * this->ncol; }

		//------------------------------------------------------------------------
		// Destructor
		//------------------------------------------------------------------------
		~MATRIX(  ) { finalize( this->mtr ); }
};

#endif // __MATRIX_H__